#! /bin/sh -
#
# Builder of custom stages (cross compilers, GNU/Linux distributions)
#
# Copyright (c) 2014-2020 Matias Fonzo, <selk@dragora.org>.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# EXIT STATUS
# 0  = Successful completion
# 1  = Minor common errors (e.g: help usage, support not available)
# 2  = Command execution error
# 3  = Integrity check error for compressed files

PROGRAM="${0##*/}"

# Override locale settings
LC_ALL=C
LANGUAGE=C
export LC_ALL LANGUAGE

# Get current working directory (absolute path)
CWD="$(CDPATH= cd -- $(dirname -- "$0") && pwd)"

#### Functions

usage() {
    printf "%s\n" \
     "Builder of custom stages (cross compilers, GNU/Linux distributions)"  \
     ""                                                                     \
     "Usage: $PROGRAM [OPTION]... [FILE]..."                                \
     ""                                                                     \
     "Defaults for the options are specified in brackets."                  \
     ""                                                                     \
     "Options:"                                                             \
     "  -h          display this help and exit"                             \
     "  -V          print version information and exit"                     \
     "  -a          target architecture [${arch}]"                          \
     "  -j          parallel jobs for the compiler [${jobs}]"               \
     "  -k          keep (don't delete) source directory"                   \
     "  -o          output directory [${output}]"                           \
     "  -s          stage number to build [${stage}]"                       \
     ""                                                                     \
     "Some influential environment variables:"                              \
     "  TMPDIR      temporary directory for sources [${TMPDIR}]"            \
     "  BTCC        C compiler command [${BTCC}]"                           \
     "  BTCXX       C++ compiler command [${BTCXX}]"                        \
     "  BTCFLAGS    C compiler flags [${BTCFLAGS}]"                         \
     "  BTCXXFLAGS  C++ compiler flags [${BTCXXFLAGS}]"                     \
     "  BTLDFLAGS   linker flags [${BTLDFLAGS}]"                            \
     "  VENDOR      vendor name to reflect on the target triplet"           \
     ""                                                                     \
     "Supported architectures (targets):"
     for name in "${CWD}/targets"/*
     do
         printf "%s" "${name##*/} "
     done
     unset name
    echo ""
}

version()
{
    printf "%s\n" \
     "$PROGRAM 3.21" \
     "Copyright (C) 2014-2020 Matias Andres Fonzo." \
     "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>" \
     "This is free software: you are free to change and redistribute it." \
     "There is NO WARRANTY, to the extent permitted by law."
}

warn()
{
    echo "$@" 1>&2
}

chkstatus_or_exit()
{
    status=$?

    if test $status -ne 0
    then
        echo "Return status = $status" 1>&2
        exit ${1-2};	# If not given, defaults to 2
    fi

    unset status
}

# Function to test and extract compressed files
unpack()
{
    for file in "$@"
    do
        case $file in
        *.tar)
            tar tf "$file" > /dev/null && \
            tar xpf "$file"
            chkstatus_or_exit 3
            ;;
        *.tar.gz | *.tgz | *.tar.Z )
            gzip -cd "$file" | tar tf - > /dev/null && \
            gzip -cd "$file" | tar xpf -
            chkstatus_or_exit 3
            ;;
        *.tar.bz2 | *.tbz2 | *.tbz )
            bzip2 -cd "$file" | tar tf - > /dev/null && \
            bzip2 -cd "$file" | tar xpf -
            chkstatus_or_exit 3
            ;;
        *.tar.lz | *.tlz )
            lzip -cd "$file" | tar tf - > /dev/null && \
            lzip -cd "$file" | tar xpf -
            chkstatus_or_exit 3
            ;;
        *.tar.xz | *.txz )
            xz -cd "$file" | tar tf - > /dev/null && \
            xz -cd "$file" | tar xpf -
            chkstatus_or_exit 3
            ;;
        *.zip | *.ZIP )
            unzip -t "$file" > /dev/null && \
            unzip "$file" > /dev/null
            chkstatus_or_exit 3
            ;;
        *.gz)
            gzip -t "$file" && \
            gzip -cd "$file" > "$(basename -- $file .gz)"
            chkstatus_or_exit 3
            ;;
        *.Z)
            gzip -t "$file" && \
            gzip -cd "$file" > "$(basename -- $file .Z)"
            chkstatus_or_exit 3
            ;;
        *.bz2)
            bzip2 -t "$file" && \
            bzip2 -cd "$file" > "$(basename -- $file .bz2)"
            chkstatus_or_exit 3
            ;;
        *.lz)
            lzip -t "$file" && \
            lzip -cd "$file" > "$(basename -- $file .lz)"
            chkstatus_or_exit 3
            ;;
        *.xz)
            xz -t "$file" && \
            xz -cd "$file" > "$(basename -- $file .xz)"
            chkstatus_or_exit 3
            ;;
        *)
            warn "${PROGRAM}: cannot unpack ${file}: Unsupported extension"
            exit 1
        esac
    done
    unset file
}

# Print a warning for good practices.
#
# Recommended practices is to set variables in
# **front** of `configure' or make(1), see:
#
# http://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.69/html_node/Defining-Variables.html
# http://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.69/html_node/Setting-Output-Variables.html
# http://www.gnu.org/software/make/manual/make.html#Environment

warn_flags()
{
    warn "WARNING: Environment variable '$1' is set."   \
         "This will be unset to avoid possible risks."  \
         ""
}

#### Default values

BTCC="${BTCC:=cc}"
BTCXX="${BTCXX:=c++}"
BTCFLAGS="${BTCFLAGS:=-g0 -Os}"
BTCXXFLAGS="${BTCXXFLAGS:=$BTCFLAGS}"
BTLDFLAGS="${BTLDFLAGS:=-s}"
opt_keep=opt_keep.off
stage=0
libSuffix=""
arch="$(uname -m)" || chkstatus_or_exit
jobs=1
worktree="$CWD"
output=${worktree}/OUTPUT.${PROGRAM}
TMPDIR="${TMPDIR:=${output}/sources}"

# Compose vendor name adding "-" as suffix
test -n "$VENDOR" && VENDOR="${VENDOR}-"

#### Parse options

while getopts :ha:j:ko:s:V name
do
    case $name in
    h)
        usage
        exit 0
        ;;
    a)
        arch="$OPTARG"
        ;;
    j)
        jobs="$OPTARG"
        ;;
    k)
        opt_keep=opt_keep
        ;;
    o)
        output="$OPTARG"
        ;;
    s)
        stage="$OPTARG"
        ;;
    V)
        version
        exit 0
        ;;
    :)
        warn "Option '-${OPTARG}' requires an argument"
        usage
        exit 1
        ;;
    \?)
        warn "Illegal option -- '-${OPTARG}'"
        usage
        exit 1
        ;;
    esac
done
shift $(( OPTIND - 1 ))

# Check for environment variables, print warning, unset in any case

test "${CC:+CC_defined}" = CC_defined && warn_flags CC
test "${CXX:+CXX_defined}" = CXX_defined && warn_flags CXX
test "${CFLAGS:+CFLAGS_defined}" = CFLAGS_defined && warn_flags CFLAGS
test "${CXXFLAGS:+CXXFLAGS_defined}" = CXXFLAGS_defined && warn_flags CXXFLAGS
test "${LDFLAGS:+LDFLAGS_defined}" = LDFLAGS_defined && warn_flags LDFLAGS
unset CC CXX CFLAGS CXXFLAGS LDFLAGS warn_flags

# Load target architecture-file

if test -f "${worktree}/targets/$arch"
then
    echo "${PROGRAM}: Loading target $arch ..."
    . "${worktree}/targets/$arch"
else
    warn \
     "${PROGRAM}: Target name not recognized -- '${arch}'" \
     "See '$0 -h' for more information"
    exit 1
fi

# Determine the host to use.
#
# Set up the host if a C compiler command was exported,
# otherwise, a local `cc' will be used instead

if test -n "$BTCC"
then
    host="$(${BTCC} -dumpmachine)" || chkstatus_or_exit
else
    host="$(cc -dumpmachine)" || chkstatus_or_exit
fi

# If the host and the target are the same triplet, it won't work.
# We are changing the host if it is really needed

if test "$host" = "$target"
then
    # Rename VENDOR to 'cross'.  If empty, 'cross-linux' is added
    case "${host%-*-*}" in
    *-*)
        host="$(echo "$host" | sed -e 's/-[^-]*/-cross/')"
        ;;
    *)
        host="$(echo "$host" | sed -e 's/-[^-]*/-cross-linux/')"
        ;;
    esac
fi

# Compose variables for the physical output,
# printing some useful information

crossdir=${output}/cross/${target}
rootdir=${output}/stage${stage}

printf "%s\n" \
 "BTCC: $BTCC"                  \
 "BTCXX: $BTCXX"                \
 "BTCFLAGS: $BTCFLAGS"          \
 "BTCXXFLAGS: $BTCXXFLAGS"      \
 "BTLDFLAGS: $BTLDFLAGS"        \
 "Host: $host"                  \
 "Target: $target"              \
 "Output directory: $output"    \
 "Cross  directory: $crossdir"  \
 "Root   directory: $rootdir"

# Remove write permission for group and other
umask 022

# Create required directories
mkdir -p -- "$output" "$TMPDIR"
chkstatus_or_exit

# Set default PATH, propagate variables
PATH="${crossdir}/bin:${PATH}"

export PATH VENDOR TMPDIR

# Main loop
for file in "${CWD}/stages/${stage}"/${@:-??-*}
do
    file="${file##*/}"

    if test ! -f "${CWD}/stages/${stage}/$file"
    then
        warn "${PROGRAM}: ${stage}/${file}: No such file or stage number"
        exit 1
    fi
    if test ! -x "${CWD}/stages/${stage}/$file"
    then
        warn "${PROGRAM}: ${stage}/${file}: File not executable, dodging"
        continue;
    fi

    #### Stage pre-settings

    # Create sysroot directory, recreating the symlink for the stage 0

    if test "$stage" = 0
    then
        mkdir -p -- "${crossdir}/$target" || chkstatus_or_exit

        if test ! -e "${crossdir}/${target}/usr"
        then
            ln -sf . "${crossdir}/${target}/usr" || chkstatus_or_exit
        fi

        # Ensure toolchain sanity for multilib purposes
        case $arch in
        aarch64* | arm* | i586 | *x32 | x86_64 | riscv* )
            if test ! -e "${crossdir}/lib" && test -n "$libSuffix"
            then
                ln -sf lib${libSuffix} "${crossdir}/lib" || chkstatus_or_exit
            fi
            ;;
        esac
    fi

    # Create "tools" directory, recreating the symlink for the stage 1
    if test "$stage" = 1
    then
        if test ! -d "${rootdir}/tools"
        then
            mkdir -p -- "${rootdir}/tools" || chkstatus_or_exit
        fi

        # Make required symlink
        ln -sf "${rootdir}/tools" / || chkstatus_or_exit
    fi

    echo "${PROGRAM}: Processing $file from stage $stage ..."

    # Set trap before to run the script in order to
    # catch the return status, exit code 2 if fails

    trap 'chkstatus_or_exit 2' EXIT HUP INT QUIT ABRT TERM

    # Exit immediately on any error
    set -e

    . "${CWD}/stages/${stage}/$file"

    # Deactivate shell option(s)
    set +e

    # Reset given signals
    trap - EXIT HUP INT QUIT ABRT TERM

    # Delete declared directories on the cleanup() function
    if test "$opt_keep" != opt_keep
    then
        if type cleanup 1> /dev/null 2> /dev/null
        then
            cleanup
            chkstatus_or_exit 2
            unset cleanup
        fi
    fi

    # Back to the current working directory
    cd -- "$CWD" || chkstatus_or_exit
done

